package com.sojson.util;

import java.util.Map;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

import com.sojson.constant.ConstantByMap;
import com.sojson.util.id.GUIDUtil;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

public class JwtUtil {

    /**
     * 创建Jwt
     * 
     * @return
     */
    public static String createJWT() {
        return createJWT(generateGuid());
    }

    /**
     * 创建Jwt
     * 
     * @param id
     * @return
     */
    public static String createJWT(String id) {
        // 创建一个JwtBuilder
        return createJWTAfter(createJWTBefore(id));
    }

    /**
     * 创建Jwt
     * 
     * @param id
     * @param subject
     * @return
     */
    public static String createJWT(String id, String subject) {
        // 创建一个JwtBuilder
        JwtBuilder builder = createJWTBefore(id);

        // 可以将一个对象转成Json格式字符串存到里面
        // sub(Subject): 代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串
        builder.setSubject(subject);

        return createJWTAfter(builder);
    }

    /**
     * 创建Jwt
     * 
     * @param id
     * @param subject
     * @return
     */
    public static String createJWT(String id, Map<String, Object> claims) {
        // 创建一个JwtBuilder
        JwtBuilder builder = createJWTBefore(id);

        // 可以给Map集合添加任何值,通过Claims.get("name")方法获取值
        // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
        builder.setClaims(claims);

        return createJWTAfter(builder);
    }

    /**
     * 创建Jwt
     * 
     * @param id
     * @param subject
     * @param claims
     * @return
     */
    public static String createJWT(String id, String subject, Map<String, Object> claims) {
        // 创建一个JwtBuilder
        JwtBuilder builder = createJWTBefore(id);

        // 可以将一个对象转成Json格式字符串存到里面
        // sub(Subject): 代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串
        builder.setSubject(subject);
        // 可以给Map集合添加任何值,通过Claims.get("name")方法获取值
        // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
        builder.setClaims(claims);

        return createJWTAfter(builder);
    }

    /**
     * 创建Jwt之前的操作
     * 
     * @param id TokenId
     * @return
     */
    public static JwtBuilder createJWTBefore(String id) {
        // 指定签名的时候使用的签名算法,也就是header那部分,JJwt已经将这部分内容封装好了。

        // 切记这个秘钥不能外露哦
        // 生成签名的时候使用的秘钥secret,这个方法本地封装了的,一般可以从本地配置文件中读取
        // 它就是你服务端的私钥，在任何场景都不应该流露出去
        // 一旦客户端得知这个secret,那就意味着客户端是可以自我签发jwt了
        SecretKey key = generalKey();

        // 下面就是在为payload添加各种标准声明和私有声明了
        // 这里其实就是new一个JwtBuilder
        JwtBuilder builder = Jwts.builder();
        // 设置jti(JWT ID): 是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击
        builder.setId(id);
        // 设置签名使用的签名算法和签名使用的秘钥
        builder.signWith(getSignatureAlgorithm(), key);
        return builder;
    }

    /**
     * 创建Jwt之后的操作
     * 
     * @param jwtBuilder
     * @return
     */
    public static String createJWTAfter(JwtBuilder jwtBuilder) {
        // // 生成JWT的时间
        // long nowMillis = System.currentTimeMillis();
        // Date now = new Date(nowMillis);
        // jwtBuilder.setIssuedAt(now); // iat: jwt的签发时间
        //
        // if (TOKEN_TIMEOUT >= 0) {
        // Date exp = new Date(nowMillis + TOKEN_TIMEOUT);
        // // 是一个时间戳，代表这个JWT的过期时间
        // jwtBuilder.setExpiration(exp);
        // // 是一个时间戳，代表这个JWT生效的开始时间，意味着在这个时间之前验证JWT是会失败的
        // jwtBuilder.setNotBefore(now);
        // }

        // 生成JWT,就开始压缩为xxxxxxxxxxxxxx.xxxxxxxxxxxxxxx.xxxxxxxxxxxxx这样的jwt
        return jwtBuilder.compact();
    }

    /**
     * 解析Jwt
     *
     * @param token
     * @return
     */
    public static Claims parseJWT(String token) {

        // 签名秘钥,和生成的签名的秘钥一模一样
        SecretKey key = generalKey();
        // 得到DefaultJwtParser
        JwtParser parser = Jwts.parser();
        // 设置签名的秘钥
        parser.setSigningKey(key);

        // 设置需要解析的Jwt并获取Claims
        return parser.parseClaimsJws(token).getBody();
    }

    /**
     * 由字符串生成加密key
     *
     * @return
     */
    private static SecretKey generalKey() {
        byte[] parseBase64Binary = DatatypeConverter.parseBase64Binary(ConstantByMap.TOKEN_SECRET);

        // 根据给定的字节数组使用???加密算法构造一个密钥
        // 使用encodedKey中的始于且包含 0 到前 leng个字节这是当然是所有。
        // （后面的文章中马上回推出讲解Java加密和解密的一些算法）
        return new SecretKeySpec(parseBase64Binary, getSignatureAlgorithm().getJcaName());
    }

    /**
     * 获取加密算法
     * 
     * @return
     */
    private static SignatureAlgorithm getSignatureAlgorithm() {
        return SignatureAlgorithm.HS256;
    }

    /**
     * 生成全局唯一的ID
     * 
     * @return
     */
    private static String generateGuid() {
        return GUIDUtil.generateGuid();
    }

}
